<?php

namespace App\Repositories;

use Exception;
use Illuminate\Support\Collection;
use App\Exceptions\GeneralException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Log;

/**
 * Date: 22/03/2018
 * @author George
 * @package App\Repositories
 */
abstract class BaseRepository implements RepositoryContract
{
	/**
	 * 用于保存模型实例
	 *
	 * @var Model
	 * Date: 21/03/2018
	 * @author George
	 */
	protected $model;

	/**
	 * 用于保存查询构造器实例
	 *
	 * @var Builder
	 * Date: 21/03/2018
	 * @author George
	 */
	protected $query;

	/**
	 * 保存查询的limit
	 *
	 * @var integer
	 * Date: 21/03/2018
	 * @author George
	 */
	protected $take;

	/**
	 * Array of related models to eager load.
	 *
	 * @var array
	 */
	protected $with = [];

	/**
	 * Array of one or more where clause parameters.
	 *
	 * @var array
	 */
	protected $wheres = [];

	/**
	 * Array of one or more where in clause parameters.
	 *
	 * @var array
	 */
	protected $whereIns = [];

	/**
	 * Array of one or more ORDER BY column/value pairs.
	 *
	 * @var array
	 */
	protected $orderBys = [];

	/**
	 * 用于定义查询作用域
	 *
	 * @var array
	 * Date: 21/03/2018
	 * @author George
	 */
	protected $scopes = [];

	/**
	 * BaseRepository constructor.
	 * @throws GeneralException
	 */
	public function __construct()
	{
		$this->makeModel();
	}

	/**
	 * 用于生成模型实例的抽象方法
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @return mixed
	 */
	abstract public function model();

	/**
	 * 生成模型实例
	 *
	 * Date: 22/03/2018
	 * @author George
	 * @return Model|mixed
	 * @throws GeneralException
	 */
	public function makeModel()
	{
		$model = app()->make($this->model());

		if (! $model instanceof Model) {
			throw new GeneralException("Class {$this->model()} must be an instance of ".Model::class, 500);
		}

		return $this->model = $model;
	}

	/**
	 * 获取模型所有资源
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @param array $columns
	 * @return \Illuminate\Database\Eloquent\Collection|static[]
	 */
	public function all(array $columns = ['*'])
	{
		$this->newQuery()->eagerLoad();

		$models = $this->query->get($columns);

		$this->unsetClauses();

		return $models;
	}

	/**
	 * 获取集合的汇总
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @return int
	 */
	public function count(): int
	{
		return $this->get()->count();
	}

	/**
	 * 创建一个模型资源
	 *
	 * Date: 22/03/2018
	 * @author George
	 * @param array $attributes
	 * @return $this|Model|mixed
	 * @throws GeneralException
	 */
	public function create(array $attributes)
	{
		$this->unsetClauses();

		try {
			return $this->model->create($attributes);
		} catch (Exception $exception) {
			throw new GeneralException('未知错误导致创建失败', 500);
		}
	}

	/**
	 * 创建一条或多条模型数据到数据库
	 *
	 * Date: 22/03/2018
	 * @author George
	 * @param array $collections
	 * @return Collection|mixed
	 * @throws GeneralException
	 */
	public function createMultiple(array $collections)
	{
		$models = new Collection();

		foreach ($collections as $collection) {
			$models->push($this->create($collection));
		}

		return $models;
	}

	/**
	 * 销毁指定的模型资源
	 *
	 * Date: 22/03/2018
	 * @author George
	 * @param Model $model
	 * @param bool $force
	 * @return bool|null
	 * @throws GeneralException
	 */
	public function destroy($model, $force = false)
	{
		try {
			return $force ? $model->forceDelete() : $model->delete();
		} catch (Exception $exception) {
			throw new GeneralException('未知错误导致删除失败');
		}
	}

	/**
	 * 删除模型数据
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @return boolean
	 */
	public function delete(): bool
	{
		$this->newQuery()->setClauses()->setScopes();

		$result = $this->query->delete();

		$this->unsetClauses();

		return $result;
	}

	/**
	 * 根据主键删除对应资源
	 *
	 * Date: 22/03/2018
	 * @author George
	 * @param $primary
	 * @return bool
	 * @throws GeneralException
	 */
	public function deleteByPrimary($primary): bool
	{
		$this->unsetClauses();

		$instance = $this->getByPrimary($primary);

		try {
			return $instance->delete();
		} catch (Exception $exception) {
			throw new GeneralException('未知错误导致删除失败', 500);
		}
	}

	/**
	 * 根据主键删除一组资源
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @param array $primaries
	 * @return int
	 */
	public function deleteMultipleByPrimary(array $primaries): int
	{
		return $this->model->destroy($primaries);
	}

	/**
	 * 获取第一条模型资源
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @param array $columns
	 * @return Model|static
	 */
	public function first(array $columns = ['*'])
	{
		$this->newQuery()->eagerLoad()->setClauses()->setScopes();

		$model = $this->query->firstOrFail($columns);

		$this->unsetClauses();

		return $model;
	}

	/**
	 * 获取模型数据集合
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @param array $columns
	 * @return \Illuminate\Database\Eloquent\Collection|static[]
	 */
	public function get(array $columns = ['*'])
	{
		$this->newQuery()->eagerLoad()->setClauses()->setScopes();

		$models = $this->query->get($columns);

		$this->unsetClauses();

		return $models;
	}

	/**
	 * 根据主键获取对应的模型实例
	 *
	 * Date: 22/03/2018
	 * @author George
	 * @param $primary
	 * @param array $columns
	 * @return Model|mixed|null|object|static
	 * @throws GeneralException
	 */
	public function getByPrimary($primary, array $columns = ['*'])
	{
		$this->unsetClauses();

		$this->newQuery()->eagerLoad();

		$result = $this->query->where($this->model->getKeyName(), $primary)->first($columns);

		if ($result) {
			return $result;
		}

		throw new GeneralException('资源不存在', 404);
	}

	/**
	 * @param       $item
	 * @param       $column
	 * @param array $columns
	 *
	 * @return Model|null|static
	 */
	public function getByColumn($item, $column, array $columns = ['*'])
	{
		$this->unsetClauses();

		$this->newQuery()->eagerLoad();

		return $this->query->where($column, $item)->first($columns);
	}

	/**
	 * @param int    $limit
	 * @param array  $columns
	 * @param string $pageName
	 * @param null   $page
	 *
	 * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
	 */
	public function paginate($limit = 25, array $columns = ['*'], $pageName = 'page', $page = null)
	{
		$this->newQuery()->eagerLoad()->setClauses()->setScopes();

		$models = $this->query->paginate($limit, $columns, $pageName, $page);

		$this->unsetClauses();

		return $models;
	}

	/**
	 * 根据主键更新资源
	 *
	 * Date: 22/03/2018
	 * @author George
	 * @param $primary
	 * @param array $attributes
	 * @param array $options
	 * @return BaseRepository|Model|mixed|null|object
	 * @throws GeneralException
	 */
	public function updateByPrimary($primary, array $attributes, array $options = [])
	{
		$this->unsetClauses();

		$model = $this->getByPrimary($primary);

		try {
			$model->update($attributes, $options);
			return $model;
		} catch (Exception $exception) {
			Log::error($exception->getMessage());
			throw new GeneralException('未知错误导致更新失败', 500);
		}
	}

	/**
	 * 设置查询大小
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @param $limit
	 * @return $this
	 */
	public function limit($limit)
	{
		$this->take = $limit;
		return $this;
	}

	/**
	 * 设置排序方式
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @param $column
	 * @param string $direction
	 * @return $this
	 */
	public function orderBy($column, $direction = 'asc')
	{
		$this->orderBys[] = compact('column', 'direction');

		return $this;
	}

	/**
	 * Date: 21/03/2018
	 * @author George
	 * @param $column
	 * @param $value
	 * @param string $operator
	 * @return $this
	 */
	public function where($column, $operator = '=', $value = null)
	{
		$this->wheres[] = compact('column', 'operator', 'value');

		return $this;
	}

	/**
	 * Date: 21/03/2018
	 * @author George
	 * @param $column
	 * @param $values
	 * @return $this
	 */
	public function whereIn($column, $values)
	{
		$values = is_array($values) ? $values : [$values];

		$this->whereIns[] = compact('column', 'values');

		return $this;
	}

	/**
	 * 预加载闭包
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @param $relations
	 * @return $this
	 */
	public function with($relations)
	{
		if (is_string($relations)) {
			$relations = func_get_args();
		}

		$this->with = $relations;

		return $this;
	}

	/**
	 * 生成Query Builder
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @return $this
	 */
	protected function newQuery()
	{
		$this->query = $this->model->newQuery();

		return $this;
	}

	/**
	 * 加载多个关联关系
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @return $this
	 */
	protected function eagerLoad()
	{
		foreach ($this->with as $relation) {
			$this->query->with($relation);
		}

		return $this;
	}

	/**
	 * 生成查询语句
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @return $this
	 */
	protected function setClauses()
	{
		foreach ($this->wheres as $where) {
			$this->query->where($where['column'], $where['operator'] ?: $where['value'], $where['operator'] ? $where['value'] : null);
		}

		foreach ($this->whereIns as $whereIn) {
			$this->query->whereIn($whereIn['column'], $whereIn['values']);
		}

		foreach ($this->orderBys as $orders) {
			$this->query->orderBy($orders['column'], $orders['direction']);
		}

		if (isset($this->take) and ! is_null($this->take)) {
			$this->query->take($this->take);
		}

		return $this;
	}

	/**
	 * 设置查询作用域
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @return $this
	 */
	protected function setScopes()
	{
		foreach ($this->scopes as $method => $args) {
			$this->query->$method(implode(', ', $args));
		}

		return $this;
	}

	/**
	 * 重置查询参数
	 *
	 * Date: 21/03/2018
	 * @author George
	 * @return $this
	 */
	protected function unsetClauses()
	{
		$this->wheres = [];
		$this->whereIns = [];
		$this->scopes = [];
		$this->take = null;

		return $this;
	}

	/**
	 * 序列化属性为模型
	 *
	 * Date: 22/03/2018
	 * @author George
	 * @param array $attributes
	 * @return Model
	 */
	abstract protected function serialization(array $attributes);
}
